package com.github.elliot.gatewaycenter.security.filters;

import com.github.elliot.domain.PermissionVO;
import com.github.elliot.gatewaycenter.common.ResponseEnum;
import com.github.elliot.gatewaycenter.common.constant.AuthConstant;
import com.github.elliot.gatewaycenter.domain.AdminUserPO;
import com.github.elliot.gatewaycenter.security.converter.JwtAuthenticationConverter;
import com.github.elliot.gatewaycenter.security.handlers.CustomServerAuthenticationFailureHandler;
import com.github.elliot.gatewaycenter.security.jwt.JwtTokenGenerator;
import com.github.elliot.gatewaycenter.security.service.SecurityUserDetailService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.util.HashSet;
import java.util.Objects;

import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult;

@Slf4j
public class JwtAuthenticationFilter extends AuthenticationWebFilter {

    public JwtAuthenticationFilter(JwtAuthenticationManager jwtAuthenticationManager,
                                   JwtAuthenticationConverter jwtAuthenticationConverter,
                                   CustomServerAuthenticationFailureHandler customServerAuthenticationFailureHandler) {
        super(jwtAuthenticationManager);
        this.setRequiresAuthenticationMatcher(exchange ->
                                    Mono.justOrEmpty(exchange.getRequest().getHeaders())
                                        .mapNotNull(headers -> headers.getFirst(HttpHeaders.AUTHORIZATION))
                                        .filter(Objects::nonNull)
                                        .flatMap(token -> {
                                            log.info(token);
                                            return StringUtils.isNoneBlank(token)
                                                    && token.startsWith(AuthConstant.BEARER_PREFIX)
                                                    ? MatchResult.match() : MatchResult.notMatch();
                                        })
                                        .switchIfEmpty(MatchResult.notMatch()));
        this.setServerAuthenticationConverter(jwtAuthenticationConverter);
        this.setAuthenticationFailureHandler(customServerAuthenticationFailureHandler);

    }


    @Component
    public static class JwtAuthenticationManager implements ReactiveAuthenticationManager {

        private final JwtTokenGenerator jwtTokenGenerator;

        @Autowired
        private SecurityUserDetailService securityUserDetailService;

        public JwtAuthenticationManager(JwtTokenGenerator jwtTokenGenerator) {
            this.jwtTokenGenerator = jwtTokenGenerator;
        }

        @Override
        public Mono<Authentication> authenticate(Authentication authentication) {
            return Mono.justOrEmpty((String) authentication.getCredentials())
                    .flatMap(token -> Mono.just(jwtTokenGenerator.decodeAndVerify(token)))
                    .filter(Objects::nonNull)
                    .map(jsonObject -> jsonObject.to(AdminUserPO.class))
                    .onErrorMap(Exception.class, e -> new BadCredentialsException(ResponseEnum.AUTHENTICATION_FAILED.getMsg(),e))
                    .flatMap(adminUserPO -> {
                        String userNo = adminUserPO.getUserNo();
                        return securityUserDetailService.findBackendUrlByUserNo(userNo)
                                .map(PermissionVO::getBackendUrl)
                                .collectList()
                                .map(backendUrlList -> {
                                    adminUserPO.setBackendUrls(new HashSet<>(backendUrlList));
                                    return UsernamePasswordAuthenticationToken.authenticated(adminUserPO, null, adminUserPO.getAuthorities());
                                });
                    });
        }
    }





}